package whereRU.frontEnd;

import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.*;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import overlays.AreasOverlay;
import overlays.MyItemizedOverlay;
import overlays.RouteOverlay;
import overlays.SpaceAreasOverlay;
import routeFunctions.Loc;
import routeFunctions.OutdoorRoute;
import routeFunctions.Route;
import whereRU.frontEnd.R;
import XMLread.*;
import android.content.Intent;
import android.content.res.AssetManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.maps.*;
import database.MapDbAdapter;

public class MapViewActivity extends MapActivity{
	private MapView mMap;
	private ArrayList<GeoPoint> route;
	private ArrayList<GeoPoint> outdoorRoute;
	private MapDbAdapter mapDbAdapter;
	private boolean usingLocation;
	private Loc userLoc;
	private Loc start;
	private Loc end;

	Bundle bundle;

	//no room tag
	public static final String NO_ROOMS = "N/A";

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		bundle = savedInstanceState;
		super.onCreate(savedInstanceState);
		setContentView(R.layout.map_view);
		mMap = (MapView) findViewById(R.id.mapView);
		mMap.getOverlays().clear();
		mMap.displayZoomControls(true);
		mMap.setBuiltInZoomControls(true);
		mMap.setSatellite(true);
		route = new ArrayList<GeoPoint>();
	}

	/**
	 * Depending on the bundle received from previous activity, this will draw a route on a mapview from the specified locations
	 */
	@Override
	protected void onStart() {
		super.onStart();

		mapDbAdapter = new MapDbAdapter(this);
		mapDbAdapter.open();

		Intent intent = getIntent();
		Bundle extras = intent.getExtras();

		String sEndBuilding = extras.getString(DirectionsActivity.EXTRA_END_BUILDING);
		String sEndRoom = extras.getString(DirectionsActivity.EXTRA_END_ROOM);
		String sStartBuilding = null;
		String sStartRoom = null;

		boolean usingOutdoorRoute = true;
		

		
		if(extras.containsKey(DirectionsActivity.EXTRA_START_BUILDING)) {
			sStartBuilding = extras.getString(DirectionsActivity.EXTRA_START_BUILDING);

			sStartRoom = extras.getString(DirectionsActivity.EXTRA_START_ROOM);

			sStartRoom = extras.getString(DirectionsActivity.EXTRA_START_ROOM);

			usingLocation = false;

		}
		else {
			userLoc = (Loc) ((MyApplication) this.getApplication()).getUserLocation();
			usingLocation = true;
		}


		AssetManager assets = getResources().getAssets();
		Drawable torch = this.getResources().getDrawable(R.drawable.ic_torch);
		ParseXML parser = new ParseXML();


		BuildingCSV destBuilding = mapDbAdapter.getBuilding(sEndBuilding);
		BuildingCSV startBuilding = null;		
		if(!usingLocation)
			startBuilding = mapDbAdapter.getBuilding(sStartBuilding);

		ArrayList<Space> destBuildingSpaces 	= null;
		ArrayList<Space> startBuildingSpaces 	= null;
		Route routeOutOfStart = 	new Route();
		Route routeOutOfEnd = 		new Route();
		Route routeWithinBuilding = new Route();
		Space endSpace 		= null;
		Space startSpace	= null;
		Building startBuild = null;
		Building endBuild 	= null;


		Map<String, List<GeoPoint>> areaOverlays = new HashMap<String, List<GeoPoint>>();
		Map<String, List<GeoPoint>> startFloorOverlays = new HashMap<String, List<GeoPoint>>();
		Map<String, List<GeoPoint>> endFloorOverlays = new HashMap<String, List<GeoPoint>>();

		//Overlay our end building shape
		ArrayList<GeoPoint> geoPnts = new ArrayList<GeoPoint>();
		Loc [] shape = destBuilding.getShape();
		for (Loc l : shape) {
			geoPnts.add(l.toGeoPoint());
		}
		end = averagePoints(shape);
		areaOverlays.put(sEndBuilding, geoPnts);

		//end room is specified try to get the map of the building
		//if not possible set the end Location to average of the building shape
		if(!sEndRoom.equals(NO_ROOMS)){			
			destBuildingSpaces = mapDbAdapter.getBuildingSpaces(destBuilding.getBuildingid());
			String buildingMapFile = destBuilding.getBuildingid() + ".xml";
			for(int i=0; i<=destBuildingSpaces.size(); i++){
				if(destBuildingSpaces.get(i).getRoom().equals(sEndRoom)){
					endSpace = destBuildingSpaces.get(i);
					break;
				}
			}

			//overlay floor spaces with end different color 
			int floor = endSpace.getFloor();
			for(int i=0; i<destBuildingSpaces.size(); i++){
				if(destBuildingSpaces.get(i).getFloor( ) == floor)
					endFloorOverlays.put(destBuildingSpaces.get(i).getRoom(), destBuildingSpaces.get(i).getArrayList());
			}

			try{
				endBuild = parser.parse((InputStream) assets.open(buildingMapFile), destBuildingSpaces);
				//end building different from start so add path out of end building to route
				if(!usingLocation && !sStartBuilding.equals(sEndBuilding)){
					routeOutOfEnd = routeOutOfEnd.getRoute(endSpace, endBuild, new Route());
					addPathToRoute(routeOutOfEnd);

					//set end location for outdoor route
					ArrayList<Object> directions = routeOutOfEnd.getDirections();
					Exit exitFromEndBuild = (Exit) directions.get(directions.size()-1);
					end = new Loc(exitFromEndBuild.getLat(), exitFromEndBuild.getLong());
				}
			} 
			catch (IOException ex) {
				//Map for building is not in our assets directory
				Toast toast = new Toast(getApplicationContext());
				toast =	Toast.makeText(getApplicationContext(), 
						"Indoor routing not available for " + sEndBuilding, Toast.LENGTH_LONG);
				toast.show();

				end = averagePoints(destBuilding.getShape());
				endBuild = null;
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			} catch (IndexOutOfBoundsException e) {
				//Map for building is not in our assets directory
				Toast toast = new Toast(getApplicationContext());
				toast =	Toast.makeText(getApplicationContext(), 
						"Indoor routing not available for " + sEndBuilding, Toast.LENGTH_LONG);
				toast.show();

				end = averagePoints(destBuilding.getShape());
				endBuild = null;
			}

			//our end building has no rooms to get to set end location to average points of this building
		}
		else {
			end = averagePoints(destBuilding.getShape());
		}



		//start building was specified and it is the same as the end building
		//we want a route inside a single building
		if(!usingLocation && !sStartRoom.equals(NO_ROOMS) && sStartBuilding.equals(sEndBuilding)) {
			usingOutdoorRoute = false;
			mMap.getOverlays().add(new AreasOverlay(areaOverlays));

			//set the start space for our route
			for(int i=0; i<=destBuildingSpaces.size(); i++){
				if(destBuildingSpaces.get(i).getRoom().equals(sStartRoom)){
					startSpace = destBuildingSpaces.get(i);
					break;
				}
			}
			start = averagePoints(startSpace.getPoints());
			mMap.getOverlays().add(new SpaceAreasOverlay(endFloorOverlays, startSpace, endSpace));

			//we have a map of the building so get the route within
			if(endBuild != null){
				end = averagePoints(endSpace.getPoints());
				routeWithinBuilding = routeWithinBuilding.getRouteWithinBuilding(startSpace, endSpace, endBuild, new Route());
				route.add(start.toGeoPoint());
				addPathToRoute(routeWithinBuilding);
				route.add(end.toGeoPoint());
			}

		}


		// the user entered the start building that is different than the end building
		else if(!usingLocation && !sStartBuilding.equals(sEndBuilding)) {
			//Overlay the start building shape
			geoPnts = new ArrayList<GeoPoint>();
			Loc [] startShape = startBuilding.getShape();
			for (Loc l : startShape) {
				geoPnts.add(l.toGeoPoint());
			}
			areaOverlays.put(sStartBuilding, geoPnts);
			mMap.getOverlays().add(new AreasOverlay(areaOverlays));
			if(!endFloorOverlays.isEmpty())
				mMap.getOverlays().add(new SpaceAreasOverlay(endFloorOverlays, endSpace));


			//the user has specified a start room
			//try to get a route out of the building
			if(!sStartRoom.equals(NO_ROOMS)) {
				startBuildingSpaces = mapDbAdapter.getBuildingSpaces(startBuilding.getBuildingid());
				String buildingMapFile = startBuilding.getBuildingid() + ".xml";
				for(int i=0; i<=startBuildingSpaces.size(); i++){
					if(startBuildingSpaces.get(i).getRoom().equals(sStartRoom)){
						startSpace = startBuildingSpaces.get(i);
						break;
					}
				}

				//overlay the start floor spaces
				int floor = startSpace.getFloor();
				for(int i=0; i<startBuildingSpaces.size(); i++){
					if(startBuildingSpaces.get(i).getFloor( )== floor)
						startFloorOverlays.put(startBuildingSpaces.get(i).getRoom(), startBuildingSpaces.get(i).getArrayList());
				}
				mMap.getOverlays().add(new SpaceAreasOverlay(startFloorOverlays, startSpace));

				try {
					startBuild = parser.parse((InputStream) assets.open(buildingMapFile), startBuildingSpaces);
					routeOutOfStart = routeOutOfStart.getRoute(startSpace, startBuild, new Route());
					addPathToRoute(routeOutOfStart);
					//set start location for outdoor route
					ArrayList<Object> directions = routeOutOfStart.getDirections();
					Exit exitFromStartBuild = (Exit) directions.get(directions.size()-1);
					start = new Loc(exitFromStartBuild.getLat(), exitFromStartBuild.getLong());

				}
				catch (IOException ex) {
					//Map for building is not in our assets directory
					Toast toast = new Toast(getApplicationContext());
					toast =	Toast.makeText(getApplicationContext(), 
							"Indoor routing not available for " + sStartBuilding, Toast.LENGTH_LONG); 
					toast.show();
					start = averagePoints(startBuilding.getShape());
					startBuild = null;
				} 
				catch (ParserConfigurationException e) {
					e.printStackTrace();
				}
				catch (SAXException e) {
					e.printStackTrace();
				} 
				catch (IndexOutOfBoundsException e) {
					//Map for building is not in our assets directory
					Toast toast = new Toast(getApplicationContext());
					toast =	Toast.makeText(getApplicationContext(), 
							"Indoor routing not available for " + sStartBuilding, Toast.LENGTH_LONG);
					toast.show();

					end = averagePoints(destBuilding.getShape());
					endBuild = null;
				}

				//our start building has no rooms to get to set end location to average points of this building
			}
			else
				start = averagePoints(startBuilding.getShape());
			
			mapDbAdapter.close();
		}

		// the user chose to use their GPS location
		else{ 
			start = userLoc;
			if(endBuild != null)
				addPathToRoute(routeOutOfEnd);
			mMap.getOverlays().add(new AreasOverlay(areaOverlays));
			if(!endFloorOverlays.isEmpty())
				mMap.getOverlays().add(new SpaceAreasOverlay(endFloorOverlays, endSpace));
		}

		DecimalFormat sixD = new DecimalFormat("###.######");
		// get an outdoor route if necessary

		if(usingOutdoorRoute) {
			double sLat = Double.valueOf(sixD.format(start.getLat())), 
					sLon = Double.valueOf(sixD.format(start.getLon())),
					eLat = Double.valueOf(sixD.format(end.getLat())), 
					eLon = Double.valueOf(sixD.format(end.getLon()));
			outdoorRoute = OutdoorRoute.getDirections(sLat, sLon, eLat, eLon);
		}


		// add the indoor and outdoor routes to the list of overlays
		mMap.getOverlays().add(new RouteOverlay(route));
		if(outdoorRoute != null)
			mMap.getOverlays().add(new RouteOverlay(outdoorRoute));


		//overlay the torches with information
		MyItemizedOverlay overlay = new MyItemizedOverlay(torch, this);	
		GeoPoint spot;

		//Start torch
		String snippet;
		if(!usingLocation){
			snippet = "Starting at " + sStartBuilding;
			if(!sStartRoom.equals(NO_ROOMS)){
				spot = averagePoints(startSpace.getPoints()).toGeoPoint();
				snippet = snippet + " Room " + sStartRoom;
			}
			else
				spot = start.toGeoPoint();
		}
		else{
			//using gps location
			spot = start.toGeoPoint();
			snippet = "Your last requested location.";
		}
		OverlayItem item = new OverlayItem(spot, "Start", snippet);
		overlay.addOverlay(item);
		mMap.getOverlays().add(overlay);

		//End torch
		if(endSpace != null)
			spot = averagePoints(endSpace.getPoints()).toGeoPoint();
		else
			spot = end.toGeoPoint();
		snippet = "Arriving at " + sEndBuilding;
		if(!sEndRoom.equals(NO_ROOMS)){
			snippet = snippet + " Room " + sEndRoom;
		}
		item = new OverlayItem(spot, "Destination", snippet);
		overlay.addOverlay(item);
		mMap.getOverlays().add(overlay);

		mMap.getController().animateTo(start.toGeoPoint());
		mMap.getController().setZoom(19);

	}

	private void addPathToRoute(Route r) {
		ArrayList<Object> directions = r.getDirections();

		for(int i = 0; i<directions.size(); i++){
			Object o = directions.get(i);

			if(o instanceof Exit){
				route.add(((Exit) o).getGeopoint());
				System.out.println(((Exit) o).getExitID());
			}
			if(o instanceof Stairwell){
				route.add(((Stairwell) o).getGeopoint());
				System.out.println(((Stairwell) o).getStairID());
			}
			if(o instanceof Intersection){
				route.add(((Intersection) o).getGeopoint());
				System.out.println(((Intersection) o).getInterID());
			}
			if(o instanceof Space){
				Loc sh = averagePoints(((Space) o).getPoints());
				route.add(new GeoPoint((int) (sh.getLat() * 1e6), (int) (sh.getLon() * 1e6)));
				System.out.println(((Space) o).getRoom());
			}
		}
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	/**
	 * Compute the average location of a given shape
	 * @param shape	the shape, in lat/longs
	 * @return a Loc object representing the estimated center of the shape
	 */
	private Loc averagePoints(Loc[] shape) {
		double totalLat = 0;
		double totalLon = 0;
		int numPoints = shape.length;
		for(int i = 0; i < numPoints; i++) {
			totalLat += shape[i].getLat();
			totalLon += shape[i].getLon();
		}
		return new Loc(totalLat/numPoints, totalLon/numPoints);			
	}

	/**
	 * The button click event handler to change the MapView between satellite view and normal map view.
	 * @param view The view that fired this event.
	 */
	public void changeView(View view) {
		Button button = (Button) findViewById(R.id.changeView);
		if(mMap.isSatellite()){
			mMap.setSatellite(false);
			button.setText(R.string.button_satView);
		}
		else{
			mMap.setSatellite(true);
			button.setText(R.string.button_mapView);
		}			
	}
}

